from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

import pygame
import sys
import math

from system.eventServer import eventServer
from system.gameServer import gameServer

class PygameGraphics:
    def __init__(self):
        pygame.init()
        glutInit(sys.argv)
        
        flags = pygame.OPENGL | pygame.DOUBLEBUF | pygame.HWSURFACE
        if gameServer.fullscreen:
            flags |= pygame.FULLSCREEN
        
        self.screen = pygame.display.set_mode((gameServer.screenw,gameServer.screenh), flags)
        pygame.display.set_caption(gameServer.windowTitle)
        
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_TEXTURE_2D)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA)
        glLineWidth(1)
        glClearColor(0,0,0,0)
        
        self.initJoysticks()
        self.initKeys()
        
        self.textures = {}
        self.recordingPath = self.recordingList = None
        self.lists = {}
        self.pickCount = 0
        
        eventServer.register("render",self.render)
        eventServer.register("update",self.update)
        eventServer.register("shutdownPlugins",self.shutdown)
        
    def shutdown(self, cmd, args):
        glDisable(GL_TEXTURE_2D)
        pygame.quit()
        
    def initJoysticks(self):
        self.joystickCount = pygame.joystick.get_count()
        print "%d joysticks found" % self.joystickCount
        
        self.joysticks = []
        for i in range(self.joystickCount):
            joystick = pygame.joystick.Joystick(i)
            joystick.init()
            self.joysticks.append(joystick)
            
    def initKeys(self):
        self.keyNameMap = {}
        for key, val in pygame.__dict__.iteritems():
            if key.startswith("K_"):
                self.keyNameMap[val] = key[2:].lower()
        
    def update(self, cmd, args):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                gameServer.shutdown = True
                
            elif event.type == pygame.KEYDOWN:
                if event.key < 256:
                    keyChar = chr(event.key)
                else:
                    keyChar = None
                
                mods = pygame.key.get_mods()
                shift = mods & pygame.KMOD_SHIFT != 0
                alt = mods & pygame.KMOD_ALT != 0
                ctrl = mods & pygame.KMOD_CTRL != 0
                    
                if self.keyNameMap.has_key(event.key):
                    keyName = self.keyNameMap[event.key]
                else:
                    keyName = ""
                    
                eventServer.pull("keyDown",{"key":event.key,"keyChar":keyChar,"keyName":keyName,"shift":shift,"ctrl":ctrl,"alt":alt})
            
            elif event.type == pygame.KEYUP:
                if event.key < 256:
                    keyChar = chr(event.key)
                else:
                    keyChar = None
                    
                mods = pygame.key.get_mods()
                shift = mods & pygame.KMOD_SHIFT != 0
                alt = mods & pygame.KMOD_ALT != 0
                ctrl = mods & pygame.KMOD_CTRL != 0
                
                if self.keyNameMap.has_key(event.key):
                    keyName = self.keyNameMap[event.key]
                else:
                    keyName = ""
                    
                eventServer.pull("keyUp",{"key":event.key,"keyChar":keyChar,"keyName":keyName,"shift":shift,"ctrl":ctrl,"alt":alt})
                    
            elif event.type == pygame.MOUSEBUTTONDOWN:
                mods = pygame.key.get_mods()
                shift = mods & pygame.KMOD_SHIFT != 0
                alt = mods & pygame.KMOD_ALT != 0
                ctrl = mods & pygame.KMOD_CTRL != 0
                
                pos = pygame.mouse.get_pos()
                x,y = self.convertMouseCoord(pos)
                #print "mouse button down", event.button, x, y
                if not self.doPicking(pos[0],pos[1],event.button,True):
                    eventServer.pull("mouseButtonDown",{"button":event.button,"x":x,"y":y,"shift":shift,"ctrl":ctrl,"alt":alt})
            
            elif event.type == pygame.MOUSEBUTTONUP:
                mods = pygame.key.get_mods()
                shift = mods & pygame.KMOD_SHIFT != 0
                alt = mods & pygame.KMOD_ALT != 0
                ctrl = mods & pygame.KMOD_CTRL != 0
                
                pos = pygame.mouse.get_pos()
                x,y = self.convertMouseCoord(pos)
                #print "mouse button down", event.button, x, y
                if not self.doPicking(pos[0],pos[1],event.button,False):
                    eventServer.pull("mouseButtonUp",{"button":event.button,"x":x,"y":y,"shift":shift,"ctrl":ctrl,"alt":alt})
            
            elif event.type == pygame.MOUSEMOTION:
                x,y = self.convertMouseCoord(event.pos)
                dx,dy = self.convertMouseCoord(event.rel)
                #print "mouse motion", x, y
                eventServer.pull("mouseMotion",{"x":x,"y":y,"dx":dx,"dy":dy})
                
            elif event.type == pygame.JOYBUTTONDOWN:
                joy = event.joy
                button = event.button
                
                #print "joyButtonDown", joy, button
                
                eventServer.pull("joyButtonDown",{"joy":joy,"button":button})
                
            elif event.type == pygame.JOYBUTTONUP:
                joy = event.joy
                button = event.button
                
                #print "joyButtonUp", joy, button
                
                eventServer.pull("joyButtonUp",{"joy":joy,"button":button})
                
            elif event.type == pygame.JOYAXISMOTION:
                joy = event.joy
                axis = event.axis
                value = event.value
                
                #print "joyAxisMotion", joy, axis, value
                
                eventServer.pull("joyAxisMotion",{"joy":joy,"axis":axis,"value":value})
                
    def convertMouseCoord(self, coord):
        return float(coord[0]) / gameServer.screenw, float(coord[1]) / gameServer.screenh
            
    def render(self, cmd, args):
        self.preRender()
        
        #self.set3dProjection()
        #self.render3d(gameServer.root)
        
        self.set2dProjection()
        self.render2d(gameServer.root)
        
        self.postRender()
        
    def preRender(self):
        self.renderCount = 0
        glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT)
        
    def postRender(self):
        glFlush()
        pygame.display.flip()

    def set3dProjection(self):
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(45.0,float(gameServer.screenw)/float(gameServer.screenh),1.0,500.0)
        
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        
    def set2dProjection(self, justModelView = False):
        if not justModelView:
            glMatrixMode(GL_PROJECTION)
            glLoadIdentity()
            gluOrtho2D(0,gameServer.screenw,0,gameServer.screenh)
        
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glTranslate(0,gameServer.screenh,0)
        glScalef(1,-1,1)
        
    def doPicking(self, x, y, button, down):
        from OpenGL import GL
        if hasattr(GL,"glSelectWithCallback"):
            selectFunction = GL.glSelectWithCallback
        else:
            selectFunction = my_glSelectWithCallback
        
        self.pickMap = {}
        self.currentName = 1
        self.pickCount += 1
        any = False
        
        def pickRender():
            self.set2dProjection(True)
            self.render2d(gameServer.root,True)
            
        buf = selectFunction(x,y,pickRender,20,20)
        hits = [i[2][0] for i in buf]
        if hits:
            for hit in hits:
                if self.pickMap.has_key(hit):
                    #print "hit", self.pickMap[hit]
                    if down:
                        s = "Down"
                    else:
                        s = "Up"
                        
                    rx = float(x) / gameServer.screenw
                    ry = float(y) / gameServer.screenh
                        
                    eventServer.pull("objectClick" + s,{"path":self.pickMap[hit],"count":self.pickCount,"x":rx,"y":ry,"button":button})
                    
                    obj = gameServer.root.getObject(self.pickMap[hit])
                    if obj and obj.hasVar("blockPick") and obj.getVar("blockPick"):
                        any = True
        
        return any
        
    def render3d(self, obj, picking = False):
        pass
    
    def render2d(self, obj, picking = False):
        objPath = obj.getVar("path")
        shown = not obj.hasVar("shown") or obj.getVar("shown")
        
        if obj.hasVar("renderOp2d") and shown:
            glPushMatrix()
            #glPushAttrib(GL_ALL_ATTRIB_BITS)
            
            renderOp = obj.getVar("renderOp2d")
            
            if renderOp == 'camera':
                x,y = obj.getArg('pos',(0,0))
                sx,sy = obj.getArg("scale",(1,1))
                #glTranslatef(-x + gameServer.screenw/2,-y + gameServer.screenh/2,0)
                
                glTranslatef(gameServer.screenw/2,gameServer.screenh/2,0)
                glScalef(1.0/sx,1.0/sy,1)
                glTranslatef(-x,-y,0)
                
            else:
                glTranslate(0,0,0.0001)
                
                if obj.hasVar("layer"):
                    glTranslate(0,0,0.5 + obj.getVar("layer") * 0.001)
    
                x,y = obj.getArg("pos",(0,0))
                glTranslatef(x,y,0)
                
                #y,p,r = [math.degrees(f) for f in obj.getArg("rot",(0,0,0))]
                #glRotatef(y,0,1,0)
                #glRotatef(p,1,0,0)
                #glRotatef(r,0,0,1)
                glRotatef(math.degrees(obj.getArg("rot",0)),0,0,1)
                
                x,y = obj.getArg("scale",(1,1))
                glScalef(x,y,1)
            
            if not picking and self.lists.has_key(objPath):
                #print "call", objPath, self.lists[objPath]
                glCallList(self.lists[objPath])
            else:
                if not picking and not self.recordingPath and obj.hasVar("static") and obj.getVar("static"):
                    self.recordingPath = objPath
                    self.recordingList = glGenLists(1)
                    glNewList(self.recordingList,GL_COMPILE_AND_EXECUTE)
                
                if not picking and obj.hasVar("color"):
                    r,g,b = obj.getVar("color")
                    glColor3f(r,g,b)
                else:
                    glColor3f(1,1,1)
                    
                if not picking and obj.hasVar("texture"):
                    self.bindTexture(obj.getVar("texture"))
                else:
                    self.unbindTexture()
                
                if picking:
                    self.pickMap[self.currentName] = objPath
                    glPushName(self.currentName)
                    self.currentName += 1
                    
                if renderOp != "sceneNode":
                    
                    funcName = 'render2d_%s' % renderOp
                    if hasattr(self,funcName) and callable(getattr(self,funcName)):
                        getattr(self,funcName)(obj)
                    elif renderOp != "camera":
                        print "ERROR: unknown 2d render operation", renderOp
            
                if picking:
                    glPopName()
                    
                for child in obj.children.itervalues():
                    self.render2d(child,picking)
                    
                if not picking and objPath == self.recordingPath:
                    glEndList()
                    self.lists[self.recordingPath] = self.recordingList
                    self.recordingList = None
                    self.recordingPath = None
                
            #glPopAttrib()
            glPopMatrix()
            
        elif shown:
            for child in obj.children.itervalues():
                self.render2d(child,picking)
                
    def render2d_lines(self, obj):
        points = obj.getArg("points",[])
        if points:
            glBegin(GL_LINES)
            for x,y, in points:
                glVertex2f(x,y)
            glEnd()
                
    def render2d_lineLoop(self, obj):
        points = obj.getArg("points",[])
        if points:
            glBegin(GL_LINE_LOOP)
            for x,y, in points:
                glVertex2f(x,y)
            glEnd()
                
    def render2d_line(self, obj):
        points = obj.getArg("points",[])
        if points:
            glBegin(GL_LINE_STRIP)
            for x,y, in points:
                glVertex2f(x,y)
            glEnd()
                
    def render2d_circle(self, obj):
        if obj.getArg("filled",True):
            glBegin(GL_TRIANGLE_FAN)
            glTexCoord2f(0.5,0.5)
            glVertex2f(0,0)
        else:
            glBegin(GL_LINE_LOOP)
            
        numSlices = obj.getArg("numSlices",25)
        for i in range(numSlices+1):
            th = float(i) / float(numSlices) * math.pi * 2
            x = math.cos(th)
            y = math.sin(th)
            glTexCoord2f((x+1.0)/2.0,(y+1.0)/2.0)
            glVertex2f(x,y)
        glEnd()
    
    def render2d_rect(self, obj):
        if obj.getArg("filled",True):
            glBegin(GL_QUADS)
        else:
            glBegin(GL_LINE_LOOP)
        glTexCoord2f(0,0)
        glVertex3f(0,0,0)
        glTexCoord2f(0,1)
        glVertex3f(0,1,0)
        glTexCoord2f(1,1)
        glVertex3f(1,1,0)
        glTexCoord2f(1,0)
        glVertex3f(1,0,0)
        glEnd()
        
    def render2d_triangle(self, obj):
        if obj.getArg("filled",True):
            glBegin(GL_QUADS)
        else:
            glBegin(GL_LINE_LOOP)
        x,y = obj.getArg("v1",(-0.5,0))
        glTexCoord2f(x,y)
        glVertex2f(x,y)
        x,y = obj.getArg("v2",(0,1))
        glTexCoord2f(x,y)
        glVertex2f(x,y)
        x,y = obj.getArg("v3",(0.5,0))
        glTexCoord2f(x,y)
        glVertex2f(x,y)
        glEnd()
        
    def render2d_text(self, obj):
        text = obj.getArg("text","")
        if not text:
            return
        
        glRasterPos(0,0,0)
        for ch in text:
            glutBitmapCharacter(GLUT_BITMAP_HELVETICA_18,ord(ch))
            
    #def render2d_text(self, obj):
        #text = obj.getArg("text","")
        #if not text:
            #return
        
        #align = obj.getArg("align", "center")
        #textLineHeight = obj.getArg("textLineHeight",15)
        
        #glPushMatrix()
        #cury = textLineHeight/-2.0
        #numLines = text.count('\n') + 1
        #lineWidth = []
        #for line in text.split('\n'):
            #width = 0
            #for i in line:
                #width += glutBitmapWidth(GLUT_BITMAP_HELVETICA_12,ord(i))
            #lineWidth.append(width)
        #for i, line in enumerate(text.split('\n')):
            #if align == 'left':
                #startx = 0
            #elif align == 'center':
                #startx =  - lineWidth[i]/2
            #else:
                #startx =  - lineWidth[i]
            #print "raster", startx, cury
            #glRasterPos(startx,cury,0)
            #cury += textLineHeight
            #for i in line:
                #glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12,ord(i))
        #glPopMatrix()
        
    def render2d_custom(self, obj):
        from OpenGL import GL
        glBegin(GL.__dict__['GL_' + obj.getVar("renderType").upper()])
        for x,y in obj.getVar("points"):
            glVertex2f(x,y)
        glEnd()

    def loadTexture(self, filename):
        fname = "dat/tex/%s" % filename
        rawtex = pygame.image.load(fname)
        texdat = pygame.image.tostring(rawtex,"RGBA",1)
        tex = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D,tex)
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR)
        glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,64,64,0,GL_RGBA,GL_UNSIGNED_BYTE,texdat)
        return tex
    
    def bindTexture(self, filename):
        if not self.textures.has_key(filename):
            self.textures[filename] = self.loadTexture(filename)
        glBindTexture(GL_TEXTURE_2D,self.textures[filename])
        
    def unbindTexture(self):
        glBindTexture(GL_TEXTURE_2D,0)

# glSelectWithCallback utility
def my_glSelectWithCallback(x, y, callback, xsize = 5, ysize = 5,
buffer_size = 512):
       '''glSelectWithCallback(int x, int y, Callable callback, int
xsize=5, int ysize=5)
 x,y -- x and y window coordinates for the center of the pick box
 rendercallback -- callback (callable Python object) taking 0 arguments
   which performs pick-mode rendering
 xsize,ysize -- x and y dimensions of the pick box (default = 5x5)
The function returns a tuple (possibly empty) of:
 ( (minimumzdepth, maximumzdepth, (name, name, name),...)
   minimumzdepth, maximumzdepth -- depths in integer format
     If you want the more traditional 0.0 to 1.0 numbers, divide
         by (2**32)-1
     If you want the physical depth, multiply that by the frustrum depth and
       add your near clipping plane.
   name -- the names (integers) used in calls to glPushName( int )'''
       # this import needs to be late binding or Python gets stuck in an infinite import loop
       from OpenGL.GLU import gluPickMatrix
       viewport = glGetIntegerv(GL_VIEWPORT)
       glSelectBuffer(buffer_size)
       glRenderMode(GL_SELECT)
       glInitNames()
       glMatrixMode(GL_PROJECTION)
       previousviewmatrix = glGetDoublev(GL_PROJECTION_MATRIX)
       glLoadIdentity()
       gluPickMatrix(x, viewport[3] - y, xsize, ysize, viewport)
       glMultMatrixd(previousviewmatrix)
       callback()
       glFlush()
       glMatrixMode(GL_PROJECTION)
       glLoadMatrixd(previousviewmatrix)
       return glRenderMode(GL_RENDER)
